home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Applications / Python 1.3.3 / Python 133 68K / Demo / pdist / rcvs.py < prev    next >
Text File  |  1996-05-20  |  11KB  |  482 lines

  1. #! /usr/local/bin/python
  2.  
  3. """Remote CVS -- command line interface"""
  4.  
  5. # XXX To do:
  6. #
  7. # Bugs:
  8. # - if the remote file is deleted, "rcvs update" will fail
  9. #
  10. # Functionality:
  11. # - cvs rm
  12. # - descend into directories (alraedy done for update)
  13. # - conflict resolution
  14. # - other relevant commands?
  15. # - branches
  16. #
  17. # - Finesses:
  18. # - retain file mode's x bits
  19. # - complain when "nothing known about filename"
  20. # - edit log message the way CVS lets you edit it
  21. # - cvs diff -rREVA -rREVB
  22. # - send mail the way CVS sends it
  23. #
  24. # Performance:
  25. # - cache remote checksums (for every revision ever seen!)
  26. # - translate symbolic revisions to numeric revisions
  27. #
  28. # Reliability:
  29. # - remote locking
  30. #
  31. # Security:
  32. # - Authenticated RPC?
  33.  
  34.  
  35. from cvslib import CVS, File
  36. import md5
  37. import os
  38. import string
  39. import sys
  40. from cmdfw import CommandFrameWork
  41.  
  42.  
  43. DEF_LOCAL = 1                # Default -l
  44.  
  45.  
  46. class MyFile(File):
  47.  
  48.     def action(self):
  49.         """Return a code indicating the update status of this file.
  50.  
  51.         The possible return values are:
  52.         
  53.         '=' -- everything's fine
  54.         '0' -- file doesn't exist anywhere
  55.         '?' -- exists locally only
  56.         'A' -- new locally
  57.         'R' -- deleted locally
  58.         'U' -- changed remotely, no changes locally
  59.                (includes new remotely or deleted remotely)
  60.         'M' -- changed locally, no changes remotely
  61.         'C' -- conflict: changed locally as well as remotely
  62.                (includes cases where the file has been added
  63.                or removed locally and remotely)
  64.         'D' -- deleted remotely
  65.         'N' -- new remotely
  66.         'r' -- get rid of entry
  67.         'c' -- create entry
  68.         'u' -- update entry
  69.  
  70.         (and probably others :-)
  71.         """
  72.         if not self.lseen:
  73.             self.getlocal()
  74.         if not self.rseen:
  75.             self.getremote()
  76.         if not self.eseen:
  77.             if not self.lsum:
  78.                 if not self.rsum: return '0' # Never heard of
  79.                 else:
  80.                     return 'N' # New remotely
  81.             else: # self.lsum
  82.                 if not self.rsum: return '?' # Local only
  83.                 # Local and remote, but no entry
  84.                 if self.lsum == self.rsum:
  85.                     return 'c' # Restore entry only
  86.                 else: return 'C' # Real conflict
  87.         else: # self.eseen
  88.             if not self.lsum:
  89.                 if self.edeleted:
  90.                     if self.rsum: return 'R' # Removed
  91.                     else: return 'r' # Get rid of entry
  92.                 else: # not self.edeleted
  93.                     if self.rsum:
  94.                         print "warning:",
  95.                         print self.file,
  96.                         print "was lost"
  97.                         return 'U'
  98.                     else: return 'r' # Get rid of entry
  99.             else: # self.lsum
  100.                 if not self.rsum:
  101.                     if self.enew: return 'A' # New locally
  102.                     else: return 'D' # Deleted remotely
  103.                 else: # self.rsum
  104.                     if self.enew:
  105.                         if self.lsum == self.rsum:
  106.                             return 'u'
  107.                         else:
  108.                             return 'C'
  109.                     if self.lsum == self.esum:
  110.                         if self.esum == self.rsum:
  111.                             return '='
  112.                         else:
  113.                             return 'U'
  114.                     elif self.esum == self.rsum:
  115.                         return 'M'
  116.                     elif self.lsum == self.rsum:
  117.                         return 'u'
  118.                     else:
  119.                         return 'C'
  120.  
  121.     def update(self):
  122.         code = self.action()
  123.         if code == '=': return
  124.         print code, self.file
  125.         if code in ('U', 'N'):
  126.             self.get()
  127.         elif code == 'C':
  128.             print "%s: conflict resolution not yet implemented" % \
  129.                   self.file
  130.         elif code == 'D':
  131.             remove(self.file)
  132.             self.eseen = 0
  133.         elif code == 'r':
  134.             self.eseen = 0
  135.         elif code in ('c', 'u'):
  136.             self.eseen = 1
  137.             self.erev = self.rrev
  138.             self.enew = 0
  139.             self.edeleted = 0
  140.             self.esum = self.rsum
  141.             self.emtime, self.ectime = os.stat(self.file)[-2:]
  142.             self.extra = ''
  143.  
  144.     def commit(self, message = ""):
  145.         code = self.action()
  146.         if code in ('A', 'M'):
  147.             self.put(message)
  148.             return 1
  149.         elif code == 'R':
  150.             print "%s: committing removes not yet implemented" % \
  151.                   self.file
  152.         elif code == 'C':
  153.             print "%s: conflict resolution not yet implemented" % \
  154.                   self.file
  155.  
  156.     def diff(self, opts = []):
  157.         self.action()        # To update lseen, rseen
  158.         flags = ''
  159.         rev = self.rrev
  160.         # XXX should support two rev options too!
  161.         for o, a in opts:
  162.             if o == '-r':
  163.                 rev = a
  164.             else:
  165.                 flags = flags + ' ' + o + a
  166.         if rev == self.rrev and self.lsum == self.rsum:
  167.             return
  168.         flags = flags[1:]
  169.         fn = self.file
  170.         data = self.proxy.get((fn, rev))
  171.         sum = md5.new(data).digest()
  172.         if self.lsum == sum:
  173.             return
  174.         import tempfile
  175.         tfn = tempfile.mktemp()
  176.         try:
  177.             tf = open(tfn, 'w')
  178.             tf.write(data)
  179.             tf.close()
  180.             print 'diff %s -r%s %s' % (flags, rev, fn)
  181.             sts = os.system('diff %s %s %s' % (flags, tfn, fn))
  182.             if sts:
  183.                 print '='*70
  184.         finally:
  185.             remove(tfn)
  186.  
  187.     def commitcheck(self):
  188.         return self.action() != 'C'
  189.  
  190.     def put(self, message = ""):
  191.         print "Checking in", self.file, "..."
  192.         data = open(self.file).read()
  193.         if not self.enew:
  194.             self.proxy.lock(self.file)
  195.         messages = self.proxy.put(self.file, data, message)
  196.         if messages:
  197.             print messages
  198.         self.setentry(self.proxy.head(self.file), self.lsum)
  199.     
  200.     def get(self):
  201.         data = self.proxy.get(self.file)
  202.         f = open(self.file, 'w')
  203.         f.write(data)
  204.         f.close()
  205.         self.setentry(self.rrev, self.rsum)
  206.  
  207.     def log(self, otherflags):
  208.         print self.proxy.log(self.file, otherflags)
  209.  
  210.     def add(self):
  211.         self.eseen = 0        # While we're hacking...
  212.         self.esum = self.lsum
  213.         self.emtime, self.ectime = 0, 0
  214.         self.erev = ''
  215.         self.enew = 1
  216.         self.edeleted = 0
  217.         self.eseen = 1        # Done
  218.         self.extra = ''
  219.  
  220.     def setentry(self, erev, esum):
  221.         self.eseen = 0        # While we're hacking...
  222.         self.esum = esum
  223.         self.emtime, self.ectime = os.stat(self.file)[-2:]
  224.         self.erev = erev
  225.         self.enew = 0
  226.         self.edeleted = 0
  227.         self.eseen = 1        # Done
  228.         self.extra = ''
  229.  
  230.  
  231. SENDMAIL = "/usr/lib/sendmail -t"
  232. MAILFORM = """To: %s
  233. Subject: CVS changes: %s
  234.  
  235. ...Message from rcvs...
  236.  
  237. Committed files:
  238.     %s
  239.  
  240. Log message:
  241.     %s
  242. """
  243.  
  244.  
  245. class RCVS(CVS):
  246.  
  247.     FileClass = MyFile
  248.  
  249.     def __init__(self):
  250.         CVS.__init__(self)
  251.  
  252.     def update(self, files):
  253.         for e in self.whichentries(files, 1):
  254.             e.update()
  255.  
  256.     def commit(self, files, message = ""):
  257.         list = self.whichentries(files)
  258.         if not list: return
  259.         ok = 1
  260.         for e in list:
  261.             if not e.commitcheck():
  262.                 ok = 0
  263.         if not ok:
  264.             print "correct above errors first"
  265.             return
  266.         if not message:
  267.             message = raw_input("One-liner: ")
  268.         committed = []
  269.         for e in list:
  270.             if e.commit(message):
  271.                 committed.append(e.file)
  272.         self.mailinfo(committed, message)
  273.  
  274.     def mailinfo(self, files, message = ""):
  275.         towhom = "sjoerd@cwi.nl, jack@cwi.nl" # XXX
  276.         mailtext = MAILFORM % (towhom, string.join(files),
  277.                     string.join(files), message)
  278.         print '-'*70
  279.         print mailtext
  280.         print '-'*70
  281.         ok = raw_input("OK to mail to %s? " % towhom)
  282.         if string.lower(string.strip(ok)) in ('y', 'ye', 'yes'):
  283.             p = os.popen(SENDMAIL, "w")
  284.             p.write(mailtext)
  285.             sts = p.close()
  286.             if sts:
  287.                 print "Sendmail exit status %s" % str(sts)
  288.             else:
  289.                 print "Mail sent."
  290.         else:
  291.             print "No mail sent."
  292.  
  293.     def report(self, files):
  294.         for e in self.whichentries(files):
  295.             e.report()
  296.  
  297.     def diff(self, files, opts):
  298.         for e in self.whichentries(files):
  299.             e.diff(opts)
  300.  
  301.     def add(self, files):
  302.         if not files:
  303.             raise RuntimeError, "'cvs add' needs at least one file"
  304.         list = []
  305.         for e in self.whichentries(files, 1):
  306.             e.add()
  307.  
  308.     def rm(self, files):
  309.         if not files:
  310.             raise RuntimeError, "'cvs rm' needs at least one file"
  311.         raise RuntimeError, "'cvs rm' not yet imlemented"
  312.  
  313.     def log(self, files, opts):
  314.         flags = ''
  315.         for o, a in opts:
  316.             flags = flags + ' ' + o + a
  317.         for e in self.whichentries(files):
  318.             e.log(flags)
  319.  
  320.     def whichentries(self, files, localfilestoo = 0):
  321.         if files:
  322.             list = []
  323.             for file in files:
  324.                 if self.entries.has_key(file):
  325.                     e = self.entries[file]
  326.                 else:
  327.                     e = self.FileClass(file)
  328.                     self.entries[file] = e
  329.                 list.append(e)
  330.         else:
  331.             list = self.entries.values()
  332.             for file in self.proxy.listfiles():
  333.                 if self.entries.has_key(file):
  334.                     continue
  335.                 e = self.FileClass(file)
  336.                 self.entries[file] = e
  337.                 list.append(e)
  338.             if localfilestoo:
  339.                 for file in os.listdir(os.curdir):
  340.                     if not self.entries.has_key(file) \
  341.                        and not self.ignored(file):
  342.                         e = self.FileClass(file)
  343.                         self.entries[file] = e
  344.                         list.append(e)
  345.             list.sort()
  346.         if self.proxy:
  347.             for e in list:
  348.                 if e.proxy is None:
  349.                     e.proxy = self.proxy
  350.         return list
  351.  
  352.  
  353. class rcvs(CommandFrameWork):
  354.  
  355.     GlobalFlags = 'd:h:p:qvL'
  356.     UsageMessage = \
  357. "usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"
  358.     PostUsageMessage = \
  359.         "If no subcommand is given, the status of all files is listed"
  360.  
  361.     def __init__(self):
  362.         """Constructor."""
  363.         CommandFrameWork.__init__(self)
  364.         self.proxy = None
  365.         self.cvs = RCVS()
  366.         
  367.     def close(self):
  368.         if self.proxy:
  369.             self.proxy._close()
  370.         self.proxy = None
  371.  
  372.     def recurse(self):
  373.         self.close()
  374.         names = os.listdir(os.curdir)
  375.         for name in names:
  376.             if name == os.curdir or name == os.pardir:
  377.                 continue
  378.             if name == "CVS":
  379.                 continue
  380.             if not os.path.isdir(name):
  381.                 continue
  382.             if os.path.islink(name):
  383.                 continue
  384.             print "--- entering subdirectory", name, "---"
  385.             os.chdir(name)
  386.             try:
  387.                 if os.path.isdir("CVS"):
  388.                     self.__class__().run()
  389.                 else:
  390.                     self.recurse()
  391.             finally:
  392.                 os.chdir(os.pardir)
  393.                 print "--- left subdirectory", name, "---"
  394.  
  395.     def options(self, opts):
  396.         self.opts = opts
  397.  
  398.     def ready(self):
  399.         import rcsclient
  400.         self.proxy = rcsclient.openrcsclient(self.opts)
  401.         self.cvs.setproxy(self.proxy)
  402.         self.cvs.getentries()
  403.  
  404.     def default(self):
  405.         self.cvs.report([])
  406.  
  407.     def do_report(self, opts, files):
  408.         self.cvs.report(files)
  409.  
  410.     def do_update(self, opts, files):
  411.         """update [-l] [-R] [file] ..."""
  412.         local = DEF_LOCAL
  413.         for o, a in opts:
  414.             if o == '-l': local = 1
  415.             if o == '-R': local = 0
  416.         self.cvs.update(files)
  417.         self.cvs.putentries()
  418.         if not local and not files:
  419.             self.recurse()
  420.     flags_update = '-lR'
  421.     do_up = do_update
  422.     flags_up = flags_update
  423.  
  424.     def do_commit(self, opts, files):
  425.         """commit [-m message] [file] ..."""
  426.         message = ""
  427.         for o, a in opts:
  428.             if o == '-m': message = a
  429.         self.cvs.commit(files, message)
  430.         self.cvs.putentries()
  431.     flags_commit = 'm:'
  432.     do_com = do_commit
  433.     flags_com = flags_commit
  434.  
  435.     def do_diff(self, opts, files):
  436.         """diff [difflags] [file] ..."""
  437.         self.cvs.diff(files, opts)
  438.     flags_diff = 'cbitwcefhnlr:sD:S:'
  439.     do_dif = do_diff
  440.     flags_dif = flags_diff
  441.  
  442.     def do_add(self, opts, files):
  443.         """add file ..."""
  444.         if not files:
  445.             print "'rcvs add' requires at least one file"
  446.             return
  447.         self.cvs.add(files)
  448.         self.cvs.putentries()
  449.  
  450.     def do_remove(self, opts, files):
  451.         """remove file ..."""
  452.         if not files:
  453.             print "'rcvs remove' requires at least one file"
  454.             return
  455.         self.cvs.remove(files)
  456.         self.cvs.putentries()
  457.     do_rm = do_remove
  458.  
  459.     def do_log(self, opts, files):
  460.         """log [rlog-options] [file] ..."""
  461.         self.cvs.log(files, opts)
  462.     flags_log = 'bhLNRtd:s:V:r:'
  463.  
  464.  
  465. def remove(fn):
  466.     try:
  467.         os.unlink(fn)
  468.     except os.error:
  469.         pass
  470.  
  471.  
  472. def main():
  473.     r = rcvs()
  474.     try:
  475.         r.run()
  476.     finally:
  477.         r.close()
  478.  
  479.  
  480. if __name__ == "__main__":
  481.     main()
  482.